<template>
  <section class="editor">
    <div class="editor__toolbar">
      <div class="toolbar__left">
        <button class="icon-btn" aria-label="Heading 1" @click="setHeading(1)">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
            <rect x="3" y="6" width="3" height="3" rx="1" />
            <rect x="8" y="6" width="13" height="3" rx="1" />
            <rect x="8" y="12" width="9" height="2" rx="1" />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Heading 2" @click="setHeading(2)">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
            <rect x="3" y="5" width="3" height="3" rx="1" />
            <rect x="3" y="11" width="3" height="3" rx="1" />
            <rect x="8" y="6" width="13" height="3" rx="1" />
            <rect x="8" y="12" width="9" height="2" rx="1" />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Heading 3" @click="setHeading(3)">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
            <rect x="3" y="4" width="3" height="3" rx="1" />
            <rect x="3" y="10" width="3" height="3" rx="1" />
            <rect x="3" y="16" width="3" height="3" rx="1" />
            <rect x="8" y="6" width="13" height="3" rx="1" />
            <rect x="8" y="12" width="9" height="2" rx="1" />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Bold" @click="exec('bold')">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M170.48,115.7A44,44,0,0,0,140,40H72a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8h80a48,48,0,0,0,18.48-92.3ZM80,56h60a28,28,0,0,1,0,56H80Zm72,136H80V128h72a32,32,0,0,1,0,64Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Italic" @click="exec('italic')">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M200,56a8,8,0,0,1-8,8H157.77L115.1,192H144a8,8,0,0,1,0,16H64a8,8,0,0,1,0-16H98.23L140.9,64H112a8,8,0,0,1,0-16h80A8,8,0,0,1,200,56Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Underline" @click="exec('underline')">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M200,224a8,8,0,0,1-8,8H64a8,8,0,0,1,0-16H192A8,8,0,0,1,200,224Zm-72-24a64.07,64.07,0,0,0,64-64V56a8,8,0,0,0-16,0v80a48,48,0,0,1-96,0V56a8,8,0,0,0-16,0v80A64.07,64.07,0,0,0,128,200Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Strikethrough" @click="exec('strikeThrough')">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M224,128a8,8,0,0,1-8,8H175.93c9.19,7.11,16.07,17.2,16.07,32,0,13.34-7,25.7-19.75,34.79C160.33,211.31,144.61,216,128,216s-32.33-4.69-44.25-13.21C71,193.7,64,181.34,64,168a8,8,0,0,1,16,0c0,17.35,22,32,48,32s48-14.65,48-32c0-14.85-10.54-23.58-38.77-32H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM76.33,104a8,8,0,0,0,7.61-10.49A17.3,17.3,0,0,1,83.11,88c0-18.24,19.3-32,44.89-32,18.84,0,34.16,7.42,41,19.85a8,8,0,0,0,14-7.7C173.33,50.52,152.77,40,128,40,93.29,40,67.11,60.63,67.11,88a33.73,33.73,0,0,0,1.62,10.49A8,8,0,0,0,76.33,104Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Unordered List" @click="exec('insertUnorderedList')">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M72,64a8,8,0,1,1-8-8A8,8,0,0,1,72,64ZM216,56H104a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM64,120a8,8,0,1,0,8,8A8,8,0,0,0,64,120Zm152-8H104a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM64,184a8,8,0,1,0,8,8A8,8,0,0,0,64,184Zm152-8H104a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Ordered List" @click="exec('insertOrderedList')">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M224,128a8,8,0,0,1-8,8H120a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM120,72h96a8,8,0,0,0,0-16H120a8,8,0,0,0,0,16Zm96,96H120a8,8,0,0,0,0,16h96a8,8,0,0,0,0-16ZM44,76H36V52a8,8,0,0,1,16,0V68H68a8,8,0,0,1,0,16H52v8a8,8,0,0,1-16,0Zm8,64H36a8,8,0,0,0,0,16H52v8a8,8,0,0,0,16,0V156H68a8,8,0,0,0,0-16H60V132A8,8,0,0,0,52,132Zm8,64H36a8,8,0,0,0,0,16H60v16a8,8,0,0,0,16,0V212A20,20,0,0,0,52,196Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="TextAa">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M87.24,52.59a8,8,0,0,0-14.48,0l-64,136a8,8,0,1,0,14.48,6.81L39.9,160h80.2l16.66,35.4a8,8,0,1,0,14.48-6.81ZM47.43,144,80,74.79,112.57,144ZM200,96c-12.76,0-22.73,3.47-29.63,10.32a8,8,0,0,0,11.26,11.36c3.8-3.77,10-5.68,18.37-5.68,13.23,0,24,9,24,20v3.22A42.76,42.76,0,0,0,200,128c-22.06,0-40,16.15-40,36s17.94,36,40,36a42.73,42.73,0,0,0,24-7.25,8,8,0,0,0,16-.75V132C240,112.15,222.06,96,200,96Zm0,88c-13.23,0-24-9-24-20s10.77-20,24-20,24,9,24,20S213.23,184,200,184Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Insert Image" @click="insertImage">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Insert Link" @click="insertLink">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M137.54,186.36a8,8,0,0,1,0,11.31l-9.94,10A56,56,0,0,1,48.38,128.4L72.5,104.28A56,56,0,0,1,149.31,102a8,8,0,1,1-10.64,12,40,40,0,0,0-54.85,1.63L59.7,139.72a40,40,0,0,0,56.58,56.58l9.94-9.94A8,8,0,0,1,137.54,186.36Zm70.08-138a56.08,56.08,0,0,0-79.22,0l-9.94,9.95a8,8,0,0,0,11.32,11.31l9.94-9.94a40,40,0,0,1,56.58,56.58L172.18,140.4A40,40,0,0,1,117.33,142,8,8,0,1,0,106.69,154a56,56,0,0,0,76.81-2.26l24.12-24.12A56.08,56.08,0,0,0,207.62,48.38Z"
            />
          </svg>
        </button>
        <button class="icon-btn" aria-label="Code">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            fill="currentColor"
            viewBox="0 0 256 256"
          >
            <path
              d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"
            />
          </svg>
        </button>
      </div>
      <!-- <button class="share-btn" @click="onShare">
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
          <path d="M229.66,109.66l-48,48A8,8,0,0,1,168,152V112h-3a88,88,0,0,0-85.23,66,8,8,0,0,1-15.5-4A103.94,103.94,0,0,1,165,96h3V56a8,8,0,0,1,13.66-5.66l48,48A8,8,0,0,1,229.66,109.66ZM192,208H40V88a8,8,0,0,0-16,0V208a16,16,0,0,0,16,16H192a8,8,0,0,0,0-16Z" />
        </svg>
        <span>Share</span>
      </button> -->
    </div>

    <div
      ref="editable"
      class="editor__content"
      contenteditable
      :placeholder="placeholder"
      @input="onInput"
      @keydown="onKeydown"
      @keydown.meta.s.prevent="onSave"
      @keydown.ctrl.s.prevent="onSave"
    >
      <h2>教材：用 easy-rich-text 完成《产品更新公告》</h2>
      <p>
        本教材将带你从零完成一份可发布的产品更新公告，练习标题/强调/列表/图片/链接与结构化卡片等能力。
      </p>
      <p>
        <img
          src="https://via.placeholder.com/1200x630?text=%E4%BA%A7%E5%93%81%E6%9B%B4%E6%96%B0%E5%85%AC%E5%91%8A+Banner"
          alt="产品更新公告 头图占位"
          style="max-width: 100%; border-radius: 8px"
        />
      </p>
      <h3>教材目标</h3>
      <ul>
        <li>掌握加粗、斜体、下划线、删除线与（有序/无序）列表。</li>
        <li>学会插入图片与链接并合理排版。</li>
        <li>用右侧“工具栏”插入并删除卡片模块。</li>
        <li>使用 <kbd>Ctrl/Cmd + S</kbd> 触发保存提示。</li>
      </ul>
      <h3>练习步骤</h3>
      <ol>
        <li>将标题修改为：<strong>产品更新公告（2025.08）</strong>。</li>
        <li>在标题下写 2–3 句引言，说明更新背景与目标，并对关键词使用<strong>加粗</strong>或<em>斜体</em>。</li>
        <li>点击上方工具条的“图片”图标，插入一张头图（可使用占位图或任意可访问链接）。</li>
        <li>添加“更新亮点”为无序列表；再用有序列表列出升级步骤。</li>
        <li>为“查看详细文档 →”添加一个链接。</li>
        <li>在右侧“工具栏”点击“+”插入一个卡片（如“SWOT 分析”），点击卡片可查看对话框；选中卡片按 Delete 可删除。</li>
        <li>按 <kbd>Ctrl/Cmd + S</kbd> 触发保存提示。</li>
        <li>对照下方验收清单自检，完成后即可作为模板复用。</li>
      </ol>
      <p>
        查看详细文档 →
        <a href="https://example.com/docs" target="_blank" rel="noopener noreferrer">https://example.com/docs</a>
      </p>
      <h3>验收清单</h3>
      <ul>
        <li>至少 1 张图片与 1 个链接。</li>
        <li>使用 2 种以上文本强调与两类列表。</li>
        <li>插入并删除过 1 张侧栏卡片，删除时出现确认。</li>
        <li>已看到“已保存（示例）”提示。</li>
      </ul>
    </div>
  </section>

  <el-dialog v-model="dialogVisible" width="520px" :title="dialogTitle">
    <p>这里展示卡片的预览或详细内容（示例）。</p>
    <template #footer>
      <el-button @click="dialogVisible=false">关闭</el-button>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import { nextTick, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

const editable = ref<HTMLDivElement | null>(null)
const placeholder = '在此开始书写...'

const dialogVisible = ref(false)
const dialogTitle = ref('卡片')

function exec(command: string, value?: string) {
  document.execCommand(command, false, value)
}

function setHeading(level: 1 | 2 | 3) {
  const root = editable.value
  if (!root) return
  root.focus()
  exec('formatBlock', `H${level}`)
  // 主动通知外部刷新 TOC（某些浏览器对 execCommand 不触发 input）
  onInput()
}

function insertImage() {
  const url = window.prompt('请输入图片地址：')
  if (url) exec('insertImage', url)
}

function insertLink() {
  const url = window.prompt('请输入链接地址：')
  if (url) exec('createLink', url)
}

function onSave() {
  ElMessage.success('已保存（示例）')
}

// 暴露给外部：在光标处插入卡片（块级，contenteditable=false），并在其后插入一个空段落供继续输入
function insertCard(type: string) {
  const root = editable.value
  if (!root) return
  root.focus()
  const sel = window.getSelection()
  let range: Range | null = null
  if (sel && sel.rangeCount > 0 && root.contains(sel.anchorNode)) {
    range = sel.getRangeAt(0)
  }
  if (!range) {
    // 若无选区，放在文末
    range = document.createRange()
    range.selectNodeContents(root)
    range.collapse(false)
    sel?.removeAllRanges()
    sel?.addRange(range)
  }

  // 找到选区所在的编辑区直接子节点（顶层块）
  let node: Node | null = range!.startContainer
  while (node && node !== root && (node as HTMLElement).parentElement !== root) {
    node = (node as HTMLElement).parentElement
  }
  let topBlock = (node && node !== root ? (node as HTMLElement) : null) || (root.lastElementChild as HTMLElement | null)

  // 构造卡片元素
  const card = document.createElement('div')
  card.className = 'rte-card'
  card.setAttribute('contenteditable', 'false')
  card.setAttribute('tabindex', '0')
  card.innerHTML = '<button class="card-delete" title="删除">×</button><div class="card-inner"><div class="icon">💡</div><div class="content"><div class="title">' + (type || '卡片') + '</div><div class="desc">点击查看详情</div></div></div>'
  card.addEventListener('click', () => {
    dialogTitle.value = (type || '卡片') + ' 详情'
    dialogVisible.value = true
  })
  const delBtn = card.querySelector('.card-delete') as HTMLButtonElement | null
  delBtn?.addEventListener('click', (e) => {
    e.stopPropagation()
    confirmRemoveCard(card)
  })

  const isHeading = (el: HTMLElement) => /^h[1-6]$/i.test(el.tagName)
  const isParagraph = (el: HTMLElement) => el.tagName.toLowerCase() === 'p'

  // 空编辑器：直接追加
  if (!topBlock) {
    root.appendChild(card)
    const p = document.createElement('p')
    p.innerHTML = '<br />'
    root.appendChild(p)
    nextTick(() => placeCaretAtStart(p))
    return
  }

  if (isParagraph(topBlock) || isHeading(topBlock)) {
    // 计算块内偏移
    const pre = range!.cloneRange()
    pre.selectNodeContents(topBlock)
    pre.setEnd(range!.startContainer, range!.startOffset)
    const before = pre.toString()
    const full = topBlock.innerText || ''
    const afterText = full.slice(before.length)

    if (!before.length) {
      // 块首：卡片在块前
      topBlock.before(card)
      const spacer = document.createElement('p')
      spacer.innerHTML = '<br />'
      card.after(spacer)
      nextTick(() => placeCaretAtStart(spacer))
      return
    }

    if (!afterText.length) {
      // 块尾：卡片在块后
      topBlock.after(card)
      const spacer = document.createElement('p')
      spacer.innerHTML = '<br />'
      card.after(spacer)
      nextTick(() => placeCaretAtStart(spacer))
      return
    }

    // 中间：拆分为左右两块
    const left = document.createElement(topBlock.tagName.toLowerCase())
    left.textContent = before
    const right = document.createElement(topBlock.tagName.toLowerCase())
    right.textContent = afterText
    topBlock.replaceWith(left)
    left.after(card)
    card.after(right)
    const spacer = document.createElement('p')
    spacer.innerHTML = '<br />'
    card.after(spacer)
    nextTick(() => placeCaretAtStart(spacer))
    return
  }

  // 其他块（列表/图片等）：在块后插入
  topBlock.after(card)
  const p = document.createElement('p')
  p.innerHTML = '<br />'
  card.after(p)
  nextTick(() => placeCaretAtStart(p))
}

function placeCaretAtStart(el: HTMLElement) {
  const sel = window.getSelection()
  const range = document.createRange()
  range.selectNodeContents(el)
  range.collapse(true)
  sel?.removeAllRanges()
  sel?.addRange(range)
}

function onKeydown(e: KeyboardEvent) {
  const root = editable.value
  if (!root) return
  if (e.key !== 'Backspace' && e.key !== 'Delete') return

  const active = document.activeElement as HTMLElement | null
  if (active && active.classList.contains('rte-card') && e.key === 'Delete') {
    e.preventDefault()
    confirmRemoveCard(active)
    return
  }

  const sel = window.getSelection()
  if (!sel || sel.rangeCount === 0) return
  const range = sel.getRangeAt(0)
  if (!root.contains(range.startContainer)) return
  // 向上找到编辑区的直接子节点
  let node: Node | null = range.startContainer
  while (node && node !== root && (node as HTMLElement).parentElement !== root) {
    node = (node as HTMLElement).parentElement
  }
  if (!node || node === root) return
  const blockEl = node as HTMLElement
  // 判断是否在块开始
  const test = document.createRange()
  test.setStart(blockEl, 0)
  test.setEnd(range.startContainer, range.startOffset)
  const atStart = (test.cloneContents().textContent || '').trim().length === 0
  if (atStart) {
    const prev = blockEl.previousElementSibling as HTMLElement | null
    if (prev && prev.classList.contains('rte-card')) {
      e.preventDefault()
      confirmRemoveCard(prev)
    }
  }
}

function confirmRemoveCard(cardEl: HTMLElement) {
  ElMessageBox.confirm('确认删除该卡片吗？', '提示', {
    type: 'warning',
    confirmButtonText: '删除',
    cancelButtonText: '取消',
    autofocus: false
  })
    .then(() => {
      const parent = cardEl.parentNode
      parent?.removeChild(cardEl)
      ElMessage.success('已删除卡片')
    })
    .catch(() => {})
}
// input 事件：通知外部重建目录（TOC）
const emit = defineEmits<{ (e: 'input'): void }>()
function onInput() {
  emit('input')
}

import { onMounted } from 'vue'
onMounted(() => {
  // 初次渲染后通知外部构建目录
  onInput()
})

defineExpose({ insertCard, getRootEl: () => editable.value })
</script>

<style lang="scss" scoped>
.editor {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 920px;
  margin: 16px auto;
}

.editor__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  border-radius: 8px;
  background: #fff;
}

.toolbar__left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  color: #111418;
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

.icon-btn:hover {
  background: #f5f7fa;
}

/* 文字图标已替换为 SVG，无需额外样式 */

.share-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px;
  background: #3d99f5;
  color: #fff;
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

.editor__content {
  min-height: 360px;
  padding: 16px;
  border-radius: 8px;
  line-height: 1.75;
  outline: none;
}

.editor__content:empty:before {
  content: attr(placeholder);
  color: #9ca3af;
}

/* 卡片样式（动态插入元素，需用 :deep 才能命中） */
.editor :deep(.rte-card) {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  margin: 6px 0;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
}
.editor :deep(.rte-card:focus) { outline: 2px solid #3d99f5; }
.editor :deep(.rte-card .card-inner) { display: flex; align-items: center; gap: 12px; }
.editor :deep(.rte-card .icon) { font-size: 18px; }
.editor :deep(.rte-card .title) { font-weight: 600; color: #111418; }
.editor :deep(.rte-card .desc) { color: #60758a; font-size: 12px; }
.editor :deep(.rte-card .card-delete) {
  position: absolute;
  right: 8px;
  top: 8px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: none;
  background: #f5f7fa;
  color: #60758a;
  cursor: pointer;
  opacity: 0;
  transition: opacity 150ms ease, background 150ms ease;
}
.editor :deep(.rte-card:hover .card-delete) { opacity: 1; }
.editor :deep(.rte-card .card-delete:hover) { background: #eceff3; }
</style>
